<script lang="tsx">
import type { PropType, Ref } from 'vue';
import type { FormActions, FormProps } from '../types/form';
import type { FormSchema } from '../types/form';
import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
import type { TableActionType } from '/@/components/Table';
import { defineComponent, computed, unref, toRefs } from 'vue';
import { Form, Col, Divider } from 'ant-design-vue';
import { componentMap } from '../componentMap';
import { BasicHelp } from '/@/components/Basic';
import { isBoolean, isFunction, isNull } from '/@/utils/is';
import { getSlot } from '/@/utils/helper/tsxHelper';
import { createPlaceholderMessage, setComponentRuleType } from '../helper';
import { upperFirst, cloneDeep } from 'lodash-es';
import { useItemLabelWidth } from '../hooks/useLabelWidth';
import { useI18n } from '/@/hooks/web/useI18n';

export default defineComponent({
    name: 'BasicFormItem',
    inheritAttrs: false,
    // props里面可以做一些类型的限制
    props: {
        // 这个可以做低代码平台了,传json用于form的解析
        schema: {
            type: Object as PropType<FormSchema>,
            default: () => ({}),
        },
        formProps: {
            type: Object as PropType<FormProps>,
            default: () => ({}),
        },
        allDefaultValues: {
            type: Object as PropType<Recordable>,
            default: () => ({}),
        },
        // formModel是表单属性的集合,一般情况下model就是指数据的集合
        formModel: {
            type: Object as PropType<Recordable>,
            default: () => ({}),
        },
        setFormModel: {
            type: Function as PropType<(key: string, value: any) => void>,
            default: null,
        },
        tableAction: {
            type: Object as PropType<TableActionType>,
        },
        formActions: {
            type: Object as PropType<FormActions>,
        },
    },
    setup(props, { slots }) {
        const { t } = useI18n();

        const { schema, formProps } = toRefs(props) as {
            schema: Ref<FormSchema>;
            formProps: Ref<FormProps>;
        };

        const itemLabelWidthProp = useItemLabelWidth(schema, formProps);

        const getValues = computed(() => {
            const { allDefaultValues, formModel, schema } = props;
            const { mergeDynamicData } = props.formProps;
            return {
                field: schema.field,
                model: formModel,
                values: {
                    ...mergeDynamicData,
                    ...allDefaultValues,
                    ...formModel,
                } as Recordable,
                schema: schema,
            };
        });

        const getComponentsProps = computed(() => {
            const { schema, tableAction, formModel, formActions } = props;
            let { componentProps = {} } = schema;
            if (isFunction(componentProps)) {
                componentProps =
                    componentProps({ schema, tableAction, formModel, formActions }) ?? {};
            }
            if (schema.component === 'Divider') {
                componentProps = Object.assign({ type: 'horizontal' }, componentProps, {
                    orientation: 'left',
                    plain: true,
                });
            }
            return componentProps as Recordable;
        });

        const getDisable = computed(() => {
            const { disabled: globDisabled } = props.formProps;
            const { dynamicDisabled } = props.schema;
            const { disabled: itemDisabled = false } = unref(getComponentsProps);
            let disabled = !!globDisabled || itemDisabled;
            if (isBoolean(dynamicDisabled)) {
                disabled = dynamicDisabled;
            }
            if (isFunction(dynamicDisabled)) {
                disabled = dynamicDisabled(unref(getValues));
            }
            return disabled;
        });

        function getShow(): { isShow: boolean; isIfShow: boolean } {
            const { show, ifShow } = props.schema;
            const { showAdvancedButton } = props.formProps;
            const itemIsAdvanced = showAdvancedButton
                ? isBoolean(props.schema.isAdvanced)
                    ? props.schema.isAdvanced
                    : true
                : true;

            let isShow = true;
            let isIfShow = true;

            if (isBoolean(show)) {
                isShow = show;
            }
            if (isBoolean(ifShow)) {
                isIfShow = ifShow;
            }
            if (isFunction(show)) {
                isShow = show(unref(getValues));
            }
            if (isFunction(ifShow)) {
                isIfShow = ifShow(unref(getValues));
            }
            isShow = isShow && itemIsAdvanced;
            return { isShow, isIfShow };
        }

        function handleRules(): ValidationRule[] {
            const {
                rules: defRules = [],
                component,
                rulesMessageJoinLabel,
                label,
                dynamicRules,
                required,
            } = props.schema;

            if (isFunction(dynamicRules)) {
                return dynamicRules(unref(getValues)) as ValidationRule[];
            }

            let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[];
            const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps;

            const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
                ? rulesMessageJoinLabel
                : globalRulesMessageJoinLabel;
            const defaultMsg = createPlaceholderMessage(component) + `${joinLabel ? label : ''}`;

            function validator(rule: any, value: any) {
                const msg = rule.message || defaultMsg;
                if (value === undefined || isNull(value)) {
                    // 空值
                    return Promise.reject(msg);
                } else if (Array.isArray(value) && value.length === 0) {
                    // 数组类型
                    return Promise.reject(msg);
                } else if (typeof value === 'string' && value.trim() === '') {
                    // 空字符串
                    return Promise.reject(msg);
                } else if (
                    typeof value === 'object' &&
                    Reflect.has(value, 'checked') &&
                    Reflect.has(value, 'halfChecked') &&
                    Array.isArray(value.checked) &&
                    Array.isArray(value.halfChecked) &&
                    value.checked.length === 0 &&
                    value.halfChecked.length === 0
                ) {
                    // 非关联选择的tree组件
                    return Promise.reject(msg);
                }
                return Promise.resolve();
            }

            const getRequired = isFunction(required) ? required(unref(getValues)) : required;

            if ((!rules || rules.length === 0) && getRequired) {
                rules = [{ required: getRequired, validator }];
            }

            const requiredRuleIndex: number = rules.findIndex(
                (rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator'),
            );

            if (requiredRuleIndex !== -1) {
                const rule = rules[requiredRuleIndex];
                const { isShow } = getShow();
                if (!isShow) {
                    rule.required = false;
                }
                if (component) {
                    if (!Reflect.has(rule, 'type')) {
                        rule.type = component === 'InputNumber' ? 'number' : 'string';
                    }

                    rule.message = rule.message || defaultMsg;

                    if (component.includes('Input') || component.includes('Textarea')) {
                        rule.whitespace = true;
                    }
                    const valueFormat = unref(getComponentsProps)?.valueFormat;
                    setComponentRuleType(rule, component, valueFormat);
                }
            }

            // Maximum input length rule check
            const characterInx = rules.findIndex((val) => val.max);
            if (characterInx !== -1 && !rules[characterInx].validator) {
                rules[characterInx].message =
                    rules[characterInx].message ||
                    t('component.form.maxTip', [rules[characterInx].max] as Recordable);
            }
            return rules;
        }

        function renderComponent() {
            const {
                renderComponentContent,
                component,
                field,
                changeEvent = 'change',
                valueField,
            } = props.schema;

            const isCheck = component && ['Switch', 'Checkbox'].includes(component);

            const eventKey = `on${upperFirst(changeEvent)}`;

            const on = {
                [eventKey]: (...args: Nullable<Recordable>[]) => {
                    const [e] = args;
                    if (propsData[eventKey]) {
                        propsData[eventKey](...args);
                    }
                    const target = e ? e.target : null;
                    const value = target ? (isCheck ? target.checked : target.value) : e;
                    props.setFormModel(field, value);
                },
            };
            const Comp = componentMap.get(component) as ReturnType<typeof defineComponent>;

            const { autoSetPlaceHolder, size } = props.formProps;
            const propsData: Recordable = {
                allowClear: true,
                getPopupContainer: (trigger: Element) => trigger.parentNode,
                size,
                ...unref(getComponentsProps),
                disabled: unref(getDisable),
            };

            const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
            // RangePicker place is an array
            if (isCreatePlaceholder && component !== 'RangePicker' && component) {
                propsData.placeholder =
                    unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component);
            }
            propsData.codeField = field;
            propsData.formValues = unref(getValues);

            const bindValue: Recordable = {
                [valueField || (isCheck ? 'checked' : 'value')]: props.formModel[field],
            };

            const compAttr: Recordable = {
                ...propsData,
                ...on,
                ...bindValue,
            };

            if (!renderComponentContent) {
                return <Comp {...compAttr} />;
            }
            const compSlot = isFunction(renderComponentContent)
                ? { ...renderComponentContent(unref(getValues)) }
                : {
                    default: () => renderComponentContent,
                };
            return <Comp {...compAttr}>{compSlot}</Comp>;
        }

        function renderLabelHelpMessage() {
            const { label, helpMessage, helpComponentProps, subLabel } = props.schema;
            const renderLabel = subLabel ? (
                <span>
                    {label} <span class="text-secondary">{subLabel}</span>
                </span>
            ) : (
                label
            );
            const getHelpMessage = isFunction(helpMessage)
                ? helpMessage(unref(getValues))
                : helpMessage;
            if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) {
                return renderLabel;
            }
            return (
                <span>
                    {renderLabel}
                    <BasicHelp
                        placement="top"
                        class="mx-1"
                        text={getHelpMessage}
                        {...helpComponentProps}
                    />
                </span>
            );
        }

        function renderItem() {
            const { itemProps, slot, render, field, suffix, component } = props.schema;
            const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
            const { colon } = props.formProps;

            if (component === 'Divider') {
                return (
                    <Col span={24}>
                        <Divider {...unref(getComponentsProps)}>{renderLabelHelpMessage()}</Divider>
                    </Col>
                );
            } else {
                const getContent = () => {
                    return slot
                        ? getSlot(slots, slot, unref(getValues))
                        : render
                            ? render(unref(getValues))
                            : renderComponent();
                };

                const showSuffix = !!suffix;
                const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;

                return (
                    <Form.Item
                        name={field}
                        colon={colon}
                        class={{ 'suffix-item': showSuffix }}
                        {...(itemProps as Recordable)}
                        label={renderLabelHelpMessage()}
                        rules={handleRules()}
                        labelCol={labelCol}
                        wrapperCol={wrapperCol}
                    >
                        <div style="display:flex">
                            <div style="flex:1;">{getContent()}</div>
                            {showSuffix && <span class="suffix">{getSuffix}</span>}
                        </div>
                    </Form.Item>
                );
            }
        }

        return () => {
            const { colProps = {}, colSlot, renderColContent, component } = props.schema;

            // 看一下使用的组件是否在其中,不在其中就无法使用了
            if (!componentMap.has(component)) {
                return null;
            }

            const { baseColProps = {} } = props.formProps;
            const realColProps = { ...baseColProps, ...colProps };
            const { isIfShow, isShow } = getShow();
            const values = unref(getValues);

            const getContent = () => {
                return colSlot
                    ? getSlot(slots, colSlot, values)
                    : renderColContent
                        ? renderColContent(values)
                        : renderItem();
            };

            return (
                isIfShow && (
                    <Col {...realColProps} v-show={isShow}>
                        {getContent()}
                    </Col>
                )
            );
        };
    },
});
</script>
